# 6. Kubernetes之深入理解Pod对象
# Pod对象
- 最小部署单元
- 一组容器的集合
- 一个Pod中的容器共享网络命名空间
- Pod是短暂的
# Pod中如何管理多个容器
- Pod中可以同时运行多个进程(作为容器运行)协同工作。
- 同一个Pod中的容器会自动的分配到同一个 node 上。
- 同一个Pod中的容器共享资源、网络环境和依赖,它们总是被同时调度。
注意在一个Pod中同时运行多个容器是一种比较高级的用法
- 只有当你的容器需要紧密配合协作的时候才考虑用这种模式
- 例如,你有一个容器作为web服务器运行,需要用到共享的volume,有另一个“sidecar”容器来从远端获取资源更新这些文件,如下图所示
# Pod中可以共享两种资源
- 网络:
- 每个Pod都会被分配一个唯一的IP地址。
- Pod中的所有容器共享网络空间,包括IP地址和端口。
- Pod内部的容器可以使用localhost(主机名)互相通信。
- Pod中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)。
- 存储:
- 可以Pod指定多个共享的Volume。
- Pod中的所有容器都可以访问共享的volume。
- Volume也可以用来持久化Pod中的存储资源,以防容器重启后文件丢失
# Pod容器分类
- Infrastructure Container:基础容器,维护整个Pod网络空间
- InitContainers:初始化容器,先于业务容器开始执行
- Containers:业务容器,并行启动
# 镜像拉取策略(imagePullPolicy)
- IfNotPresent:默认值,镜像在宿主机上不存在时才拉取
- Always:每次创建Pod 都会重新拉取一次镜像
- Never:Pod 永远不会主动拉取这个镜像
# 镜像拉取策略 - 局部
- 写在containers中,只针对containers中的镜像拉取策略生成
- 就相当于编程中的局部配置跟全局配置一样
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullPolicy: IfNotPresent
# 镜像拉取策略 - 全局
- 写在spec中,只针对spec所有的containers中的镜像拉取策略生成
- 就相当于编程中的局部配置跟全局配置一样
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey
# 资源限制
官方相关文档地址:https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
- 创建Pod的时候,可以指定计算资源(目前支持的资源类型有CPU和内存),即指定每个容器的资源请求(Request)和资源限制(Limit),资源请求是容器所需的最小资源需求,资源限制则是容器不能超过的资源上限。它们的大小关系是:0<=request<=limit<=infinity
- Pod的资源请求就是Pod中容器资源请求之和。Kubernetes在调度Pod时,会根据Node中的资源总量(通过cAdvisor接口获得),以及该Node上已使用的计算资源,来判断该Node是否满足需求。
- 资源请求能够保证Pod有足够的资源来运行,而资源限制则是防止某个Pod无限制地使用资源,导致其他Pod崩溃。特别是在公有云场景,往往会有恶意软件通过抢占内存来攻击平台。
- 原理:Docker 通过使用Linux Cgroup来实现对容器资源的控制,具体到启动参数上是--memory和--cpu-shares。Kubernetes中是通过控制这两个参数来实现对容器资源的控制。
# Pod和Container的资源请求和限制
- spec.containers[].resources.limits.cpu
- spec.containers[].resources.limits.memory
- spec.containers[].resources.requests.cpu
- spec.containers[].resources.requests.memory
# 实例
以下Pod有两个容器
- 每个Container都有0.25 cpu和64MiB内存的请求。
- 每个Container的内存限制为0.5 cpu和128MiB。
- 你可以说Pod有0.5 cpu和128 MiB内存的请求,并且限制为1 cpu和256MiB的内存
以下是yaml文件
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: db
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: wp
image: wordpress
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
# 查看node节点资源使用情况
kubectl describe pod frontend | grep -A 3 Events
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 19m default-scheduler Successfully assigned default/frontend to 192.168.0.125
kubectl describe nodes 192.168.0.125
--------- ---- ------------ ---------- --------------- ------------- ---
default frontend 500m (25%) 1 (50%) 128Mi (3%) 256Mi (6%) 20m
default tomcat-deployment-6bb6864d4f-drcjc 0 (0%) 0 (0%) 0 (0%) 0 (0%) 3h25m
default tomcat-deployment-6bb6864d4f-wmkxx 0 (0%) 0 (0%) 0 (0%) 0 (0%) 3h25m
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 500m (25%) 1 (50%)
memory 128Mi (3%) 256Mi (6%)
ephemeral-storage 0 (0%) 0 (0%)
# 因输出的内容太多了,所以我只截了部分可看的内容
# 重启策略(restartPolicy)
- Always:当容器终止退出后,总是重启容器,默认策略。
- OnFailure:当容器异常退出(退出状态码非0)时,才重启容器。
- Never:当容器终止退出,从不重启容器
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
restartPolicy: Always
## 一般都是使用默认的Always
# 健康检查(Probe)
官方相关文档地址:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
- 在实际生产环境中,想要使得开发的应用程序完全没有bug,在任何时候都运行正常,几乎是不可能的任务。
- 因此,我们需要一套管理系统,来对用户的应用程序执行周期性的健康检查和修复操作。
- 这套管理系统必须运行在应用程序之外,这一点非常重要一一如果它是应用程序的一部分,极有可能会和应用程序一起崩溃。
- 因此,在Kubernetes中,系统和应用程序的健康检查是由Kubelet来完成的。
# 二种健康检查
- 进程级健康检查
- 最简单的健康检查是进程级的健康检查,即检验容器进程是否存活。
- 这类健康检查的监控粒度是在Kubernetes集群中运行的单一容器。
- Kubelet会定期通过Docker Daemon获取所有Docker进程的运行情况,如果发现某个Docker容器未正常运行,则重新启动该容器进程。
- 目前,进程级的健康检查都是默认启用的。
- 业务级健康检查
- 在很多实际场景下,仅仅使用进程级健康检查还远远不够。
- 有时,从Docker的角度来看,容器进程依旧在运行
- 但是如果从应用程序的角度来看,代码处于死锁状态,
- 即容器永远都无法正常响应用户的业务为了解决以上问题
- Kubernetes引人了一个在容器内执行的活性探针的概念,以支持用户自己实现应用业务级的健康检查
# Probe有以下两种类型
- livenessProbe:如果检查失败,将杀死容器,根据Pod的restartPolicy来操作。
- readinessProbe:如果检查失败,Kubernetes会把Pod从service endpoints中剔除
# Probe支持三种检查方法
- Exec(ExecAction):在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
- TCPSocket(TCPSocketAction):对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
- HTTPGet(HTTPGetAction):对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于200 且小于 400,则诊断被认为是成功的。
# 一些简单的参数
- exec command 的方式探测 例如 ps 一个进程
- failureThreshold 探测几次失败 才算失败 默认是连续三次
- periodSeconds 每次的多长时间探测一次 默认10s
- timeoutSeconds 探测超市的秒数 默认1s
- initialDelaySeconds 初始化延迟探测,第一次探测的时候,因为主程序未必启动完成
- tcpSocket 检测端口的探测
- httpGet http请求探测
# exec探针检查方式的示例
# 编写yaml文件
在yaml配置文件中,您可以看到Pod具有单个Container
- 该periodSeconds字段指定kubelet应每5秒执行一次活跃度探测。
- 该initialDelaySeconds字段告诉kubelet它应该在执行第一次探测之前等待5秒。
- 要执行探测,kubelet将cat /tmp/healthy在Container中执行命令。
- 如果命令成功,则返回0,并且kubelet认为Container是活动且健康的。
- 如果该命令返回非零值,则kubelet会终止Container并重新启动它
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
# 使用yaml文件生成示例
kubectl create -f exec-liveness.yaml
pod/liveness-exec created
kubectl get pod
NAME READY STATUS RESTARTS AGE
frontend 2/2 Running 106 16h
liveness-exec 1/1 Running 0 16s
再等30秒,确认Container已重新启动,RESTARTS已增加
# kubernetes中pod创建流程
- Pod是Kubernetes中最基本的部署调度单元,可以包含container,逻辑上表示某种应用的一个实例。
- 例如一个web站点应用由前端、后端及数据库构建而成,这三个组件将运行在各自的容器中,那么我们可以创建包含三个container的pod
- 以下是创建流程图
- 客户端提交创建请求:
- 可以通过API Server的Restful API,也可以使用kubectl命令行工具。
- 支持的数据类型包括JSON和YAML。
- API Server:处理用户请求,存储Pod数据到etcd。
- 调度器:通过API Server查看未绑定的Pod,尝试为Pod分配主机。
- 过滤主机 (调度预选):
- 调度器用一组规则过滤掉不符合要求的主机。
- 比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。
- 主机打分(调度优选):
- 对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略
- 比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。
- 选择主机:选择打分最高的主机,进行binding操作,结果存储到etcd中。
- kubelet根据调度结果执行Pod创建操作:
- 绑定成功后,scheduler会调用APIServer的API在etcd中创建一个boundpod对象,描述在一个工作节点上绑定运行的所有pod信息。
- 运行在每个工作节点上的kubelet也会定期与etcd同步boundpod信息,一旦发现应该在该工作节点上运行的boundpod对象没有更新,则调用Docker API创建并启动pod内的容器。
# 调度约束
官方相关文档地址:https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
可以约束Pod (opens new window)只能在特定节点 (opens new window)上运行 ,或者更喜欢在特定节点上运行
- 有几种方法可以做到这一点,推荐的方法都使用 标签选择器 (opens new window)进行选择。
- 通常,此类约束是不必要的,因为调度程序会自动进行合理的放置
- 例如,将Pod分散到节点上,而不是将Pod放置在可用资源不足的节点上,等等
- 但是在某些情况下,您可能需要更多控制一个Pod降落的节点,
- 例如,以确保Pod最终落在连接了SSD的机器上,或将来自两个不同服务的Pod共同放置在一个可用区域中,这些服务之间的通信量很大
# 调度器的工作机制
- 预备工作
- 缓存所有的node节点,记录他们的规格:cpu、内存、磁盘空间、gpu显卡数等;
- 缓存所有运行中的pod,按照pod所在的node进行区分,统计每个node上的pod request了多少资源。request是pod的QoS配置。
- list & watch pod资源,当检查到有新的Pending状态的pod出现,就将它加入到调度队列中。
- 调度器的worker组件从队列中取出pod进行调度。
- 调度过程
- 先将当前所有的node放入队列;
- 执行predicates算法,对队列中的node进行筛选。这里算法检查了一些pod运行的必要条件,包括port不冲突、cpu和内存资源QoS(如果有的话)必须满足、挂载volume(如果有的话)类型必须匹配、nodeSelector规则必须匹配、硬性的affinity规则(下文会提到)必须匹配、node的状态(condition)必须正常,taint_toleration硬规则(下文会提到)等等。
- 执行priorities算法,对队列中剩余的node进行评分,这里有许多评分项,各个项目有各自的权重:整体cpu,内存资源的平衡性、node上是否有存在要求的镜像、同rs的pod是否有调度、node affinity的软规则、taint_toleration软规则(下文会提到)等等。
- 最终评分最高的node会被选出。即代码中suggestedHost, err := sched.schedule(pod)一句(plugin/pkg/scheduler/scheduler.go)的返回值。
- 调度器执行assume方法,该方法在pod调度到node之前,就以“该pod运行在目标node上” 为场景更新调度器缓存中的node 信息,也即预备工作中的1、2两点。这么做是为了让pod在真正调度到node上时,调度器也可以同时做后续其他pod的调度工作。
- 调度器执行bind方法,该方法创建一个Binding资源,apiserver检查到创建该资源时,会主动更新pod的nodeName字段。完成调度
# 调度约束的二种机制
- nodeName用于将Pod调度到指定的Node名称上
- nodeSelector用于将Pod调度到匹配Label的Node上
# nodeName - 强制指定
nodeName用于将Pod调度到指定的Node名称
spec.nodeName用于强制约束将Pod调度到指定的Node节点上,这里说是“调度”,但其实指定了nodeName的Pod会直接跳过Scheduler的调度逻辑,直接写入PodList列表,该匹配规则是强制匹配
apiVersion: v1
kind: Pod
metadata:
name: pod-example
labels:
app: nginx
spec:
nodeName: 192.168.0.125
containers:
- name: nginx
image: nginx:1.15
kubectl create -f pod3.yaml
pod/pod-example created
kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 10m
pod-example 1/1 Running 0 22s
kubectl describe pod pod-example
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulled 31s kubelet, 192.168.0.125 Container image "nginx:1.15" already present on machine
Normal Created 31s kubelet, 192.168.0.125 Created container
Normal Started 30s kubelet, 192.168.0.125 Started container
## 因返回结果过多,所以只截很小一部分
# nodeSelector - 匹配
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
env_role: dev
## env_role: dev, 是匹配带有这标签的node机器, 如果带有这标签的node节点有2台, 那么就会在这二台中选下部署
kubectl create -f pod2.yaml
pod/nginx created
kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 115s
kubectl describe pod nginx
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m11s default-scheduler Successfully assigned default/nginx to 192.168.0.126
Normal Pulled 2m11s kubelet, 192.168.0.126 Container image "nginx" already present on machine
Normal Created 2m10s kubelet, 192.168.0.126 Created container
Normal Started 2m10s kubelet, 192.168.0.126 Started container
## 因返回结果过多,所以只截很小一部分
# 故障排查
kubectl describe TYPE/NAME
kubectl logs TYPE/NAME [-c CONTAINER]
kubectl exec POD [-c CONTAINER] --COMMAND [args...]